﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using SlimDX;
using SlimDX.Direct3D9;
using RaygunDiplomacy.Graphics;

namespace RaygunDiplomacy
{
   public class DirectxDisplay
   {
      #region Fields

      static Layers[] layers = { Layers.Canvas, Layers.Background, Layers.Active, Layers.Foreground, Layers.UI }; 

      Image hostImage;
      D3DImage d3dImage;

      SlimDX.Direct3D9.Surface backBuffer;
      SlimDX.Direct3D9.Surface depthBuffer;

      int width = 500;
      int height = 350;

      List<IRenderer> renderers = new List<IRenderer>();

      Color4 backColor = new Color4(0, 0, 1);

      Camera camera = new Camera();
      
      
      bool surfaceChanged = true;
      bool needsReset = true;

      bool pauseRendering = true;

      #endregion

      #region Properties

      #endregion

      #region Constructor

      public DirectxDisplay(D3DImage d3dImage, Image hostImage)
      {
         System.Diagnostics.Debug.Assert(d3dImage != null);
         System.Diagnostics.Debug.Assert(hostImage != null);

         this.hostImage = hostImage;
         this.d3dImage = d3dImage;
      }

      #endregion

      #region Methods

      public void Initialize()
      {
         
         if (this.hostImage.ActualHeight > 0)
         {
            this.height = (int)this.hostImage.ActualHeight;
         }
         else if(this.d3dImage.PixelHeight > 0)
         {
            this.height = this.d3dImage.PixelHeight;
         }

         if (this.hostImage.ActualWidth > 0)
         {
            this.width = (int)this.hostImage.ActualWidth;
         }
         else if (this.d3dImage.PixelHeight > 0)
         {
            this.width = this.d3dImage.PixelWidth;
         }

         this.camera.ViewHeight = this.height;
         this.camera.ViewWidth = this.width;

         Reset();

         this.hostImage.SizeChanged += new SizeChangedEventHandler(imageHost_SizeChanged);
         
         pauseRendering = false;
      }

      public void Reset()
      {
         GraphicsProvider provider = GraphicsProvider.Instance;

         ResizeSurface();

         provider.Device.SetRenderState<Cull>(RenderState.CullMode, Cull.None);
         provider.Device.SetRenderState(RenderState.Lighting, false);

         needsReset = false;
      }

      private void ResizeSurface()
      {
         GraphicsProvider provider = GraphicsProvider.Instance;

         if (width == 0 || height == 0)
         {
            return;
         }

         if (backBuffer != null && !backBuffer.Disposed)
         {
            backBuffer.Dispose();
            backBuffer = null;
         }
         if (depthBuffer != null && !depthBuffer.Disposed)
         {
            depthBuffer.Dispose();
            depthBuffer = null;
         }
         
         backBuffer = provider.CreateRenderSurface(width, height);
         depthBuffer = provider.CreateDepthSurface(width, height);

         this.camera.ViewHeight = this.height;
         this.camera.ViewWidth = this.width;

         surfaceChanged = false;
      }

      public void Update(TimeSpan elapsed, DateTime frameNow)
      {
         foreach (IRenderer renderer in renderers)
         {
            renderer.Update(elapsed, frameNow);
         }
      }

      public void Render()
      {
         Result result;

         if (pauseRendering)
         {
            return;
         }
         GraphicsProvider provider = GraphicsProvider.Instance;

         #region Checks

         System.Diagnostics.Debug.Assert(provider.Device != null);

         if (needsReset)
         {
            Reset();
         }

         if (width == 0 || height == 0)
         {
            return;
         }

         if (surfaceChanged)
         {
            ResizeSurface();
         }

         #endregion

         if (backBuffer.ComPointer != IntPtr.Zero)
         {
            #region Camera Setup

            result = provider.Device.SetTransform(TransformState.View, camera.View);
            if (result.IsFailure)
            {
               Console.WriteLine("SetTransform(View) failed");
            }

            result = provider.Device.SetTransform(TransformState.Projection, camera.Projection);
            if (result.IsFailure)
            {
               Console.WriteLine("SetTransform(Projection) failed");
            }

            #endregion

            #region Render Target

            result = provider.Device.SetRenderTarget(0, this.backBuffer);
            if (result.IsFailure)
            {
               Console.WriteLine("SetRenderTarget failed");
            }
            provider.Device.DepthStencilSurface = this.depthBuffer;

            d3dImage.Lock();

            d3dImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, backBuffer.ComPointer);

            result = provider.Device.BeginScene();
            if (result.IsFailure)
            {
               Console.WriteLine("BeginScene failed");
            }

            result = provider.Device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, backColor, 1, 0);
            if (result.IsFailure)
            {
               Console.WriteLine("Clear failed");
            }

            #endregion

            #region Rendering

            try
            {
               var toRender = from r in renderers
                              where r.CheckVisibility(camera)
                              group r by r.Layer;

               foreach (var layer in toRender)
               {
                  foreach (var renderer in layer)
                  {
                     renderer.Render(camera);
                  }

                  SpriteDrawer.DrawAll();
               }
            }
            catch (Exception e)
            {
               Console.WriteLine(e);
            }
            #endregion
            finally
            {
               result = provider.Device.EndScene();
               if (result.IsFailure)
               {
                  Console.WriteLine("EndScene failed");
               }

               d3dImage.AddDirtyRect(new Int32Rect(0, 0, d3dImage.PixelWidth, d3dImage.PixelHeight));
               d3dImage.Unlock();
            }
         }
      }

      void imageHost_SizeChanged(object sender, SizeChangedEventArgs e)
      {
         if (this.width != (int)this.hostImage.ActualWidth || this.height != (int)this.hostImage.ActualHeight)
         {
            this.width = (int)this.hostImage.ActualWidth;
            this.height = (int)this.hostImage.ActualHeight;

            this.surfaceChanged = true;
         }
      }

      public void AddRenderer(IRenderer renderer)
      {
         if (!this.renderers.Contains(renderer))
         {
            this.renderers.Add(renderer);
         }
      }

      #endregion
   }
}
